package com.autumn.zero.application.services;

import java.util.Date;
import java.util.Map;

import org.springframework.transaction.annotation.Transactional;

import com.autumn.common.api.application.DataValidation;
import com.autumn.common.api.domain.entities.IEntity;
import com.autumn.common.api.domain.entities.auditing.IGmtDelete;
import com.autumn.common.api.domain.entities.auditing.ISoftDelete;
import com.autumn.domain.entities.EntityDataBean;
import com.autumn.domain.repositories.IEntityRepository;
import com.autumn.mybatis.criterion.EntityQuery;
import com.autumn.mybatis.criterion.LockModeEnum;
import com.autumn.mybatis.criterion.Query;
import com.autumn.mybatis.criterion.SpecifyUpdate;
import com.autumn.util.AutoMapUtils;
import com.autumn.util.DataRollbackException;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.data.DbAuditingUtils;

/**
 * 编辑抽象
 * 
 * @author 杨昌国 2018-11-19 11:49:45
 * @param <TPrimaryKey>
 *            主键类型
 * @param <TEntity>
 *            实体类型
 * @param <TRepository>
 *            仓储类型
 * @param <TAddInput>
 *            添加输入类型
 * @param <TUpdateInput>
 *            更新输入类型
 * @param <TOutput>
 *            输出类型
 * @param <TQueryEntity>
 *            查询实体类型
 * @param <TQueryRepository>
 *            查询仓储类型
 * @param <TQueryItemResult>
 *            查询项目结果类型
 */
public abstract class AbstractZeroEditAppService<TPrimaryKey, TEntity extends IEntity<TPrimaryKey>, TRepository extends IEntityRepository<TEntity, TPrimaryKey>, TAddInput, TUpdateInput extends IEntity<TPrimaryKey>, TOutput, TQueryEntity extends IEntity<TPrimaryKey>, TQueryRepository extends IEntityRepository<TQueryEntity, TPrimaryKey>, TQueryItemResult>
		extends AbstractZeroQueryAppService<TPrimaryKey, TQueryEntity, TQueryRepository, TQueryItemResult>
		implements IZeroEditAppService<TPrimaryKey, TAddInput, TUpdateInput, TOutput, TQueryItemResult> {

	private final Class<TEntity> entityClass;

	/**
	 * 获取实体类型
	 * 
	 * @return
	 */
	public final Class<TEntity> getEntityClass() {
		return this.entityClass;
	}

	private final Class<TOutput> qutputClass;

	/**
	 * 获取输出类型
	 * 
	 * @return
	 */
	public final Class<TOutput> getQutputClass() {
		return this.qutputClass;
	}

	/**
	 * 	
	 */
	@SuppressWarnings("unchecked")
	protected AbstractZeroEditAppService() {
		super();
		Map<String, Class<?>> classMap = this.getGenericActualArgumentsTypeMap();
		this.entityClass = (Class<TEntity>) classMap.get("TEntity");
		ExceptionUtils.checkNotNull(this.entityClass, "entityClass");
		this.qutputClass = (Class<TOutput>) classMap.get("TOutput");
		ExceptionUtils.checkNotNull(this.qutputClass, "qutputClass");
	}

	/**
	 * 
	 * @param entityClass
	 * @param qutputClass
	 * @param queryEntityClass
	 * @param queryItemResultClass
	 */
	protected AbstractZeroEditAppService(Class<TEntity> entityClass, Class<TOutput> qutputClass,
			Class<TQueryEntity> queryEntityClass, Class<TQueryItemResult> queryItemResultClass) {
		super(queryEntityClass, queryItemResultClass);
		this.entityClass = ExceptionUtils.checkNotNull(entityClass, "entityClass");
		this.qutputClass = ExceptionUtils.checkNotNull(qutputClass, "qutputClass");
	}

	/**
	 * 
	 * @param entityClassArgumentsName
	 * @param qutputClassArgumentsName
	 * @param queryEntityClassArgumentsName
	 * @param queryItemResultClassArgumentsName
	 */
	@SuppressWarnings("unchecked")
	protected AbstractZeroEditAppService(String entityClassArgumentsName, String qutputClassArgumentsName,
			String queryEntityClassArgumentsName, String queryItemResultClassArgumentsName) {
		super(queryEntityClassArgumentsName, queryItemResultClassArgumentsName);
		Map<String, Class<?>> classMap = this.getGenericActualArgumentsTypeMap();
		this.entityClass = (Class<TEntity>) classMap.get(entityClassArgumentsName);
		ExceptionUtils.checkNotNull(this.entityClass, "entityClass");
		this.qutputClass = (Class<TOutput>) classMap.get(qutputClassArgumentsName);
		ExceptionUtils.checkNotNull(this.qutputClass, "qutputClass");
	}

	/**
	 * 获取仓储
	 * 
	 * @return
	 */
	protected abstract TRepository getRepository();

	/**
	 * 添加之前
	 * 
	 * @param input
	 *            输入
	 * @param query
	 *            查询
	 * @return
	 */
	protected TEntity addBefore(TAddInput input, EntityQuery<TEntity> query) {
		TEntity entity = AutoMapUtils.map(input, this.getEntityClass());
		return entity;
	}

	/**
	 * 添加之后
	 * 
	 * @param input
	 *            输入
	 * @param entity
	 *            实体
	 * @param query
	 *            查询
	 * @return
	 */
	protected TOutput addAfter(TAddInput input, TEntity entity, EntityQuery<TEntity> query) {
		if (this.getEntityClass().equals(this.getQueryEntityClass())) {
			return this.toOutputByEntity(entity);
		}
		return this.queryById(entity.getId());
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public TOutput add(TAddInput input) {
		ExceptionUtils.checkNotNull(input, "input");
		if (input instanceof DataValidation) {
			DataValidation validation = (DataValidation) input;
			validation.valid();
		}
		EntityQuery<TEntity> query = new EntityQuery<>(this.getEntityClass());
		TEntity entity = addBefore(input, query);
		this.addAuditing(entity);
		this.entityHandle(entity);
		this.getRepository().insert(entity);
		query.reset();
		return this.addAfter(input, entity, query);
	}

	/**
	 * 添加审计
	 * 
	 * @param entity
	 *            实体
	 */
	protected void addAuditing(TEntity entity) {
		DbAuditingUtils.insertSetProperty(entity);
	}

	/**
	 * 更新审核
	 * 
	 * @param entity
	 *            实体
	 */
	protected void updateAuditing(TEntity entity) {
		DbAuditingUtils.updateSetProperty(entity);
	}

	/**
	 * 删除审计
	 * 
	 * @param entity
	 *            实体
	 * @return
	 */
	protected void deleteAuditing(TEntity entity) {
		DbAuditingUtils.deleteSetProperty(entity);
	}

	/**
	 * 删除审计
	 * 
	 * @param id
	 *            主键
	 * @return
	 */
	protected void deleteAuditing(TPrimaryKey id) {
		if (this.isSoftDeleteEntity()) {
			SpecifyUpdate specifyUpdate = new SpecifyUpdate(this.getEntityClass());
			specifyUpdate.eq("id", id).set("isDelete", true);
			if (IGmtDelete.class.isAssignableFrom(this.getEntityClass())) {
				specifyUpdate.set("gmtDelete", new Date());
			}
			this.getRepository().updateBySpecify(specifyUpdate.builderSection());
		}
	}

	/**
	 * 是否是软删除实体
	 * 
	 * @return
	 */
	public final boolean isSoftDeleteEntity() {
		return ISoftDelete.class.isAssignableFrom(this.getEntityClass());
	}

	/**
	 * 实体处理
	 * 
	 * @param entity
	 *            实体
	 */
	protected final void entityHandle(TEntity entity) {
		if (entity instanceof EntityDataBean) {
			EntityDataBean entityDataBean = (EntityDataBean) entity;
			entityDataBean.forNullToDefault();
		}
		if (entity instanceof DataValidation) {
			DataValidation dataValidation = (DataValidation) entity;
			dataValidation.valid();
		}
	}

	/**
	 * 更新之前
	 * 
	 * @param input
	 *            输入
	 * @param entity
	 *            已保存的实体
	 * @param query
	 *            查询
	 */
	protected void updateBefore(TUpdateInput input, TEntity entity, EntityQuery<TEntity> query) {
		AutoMapUtils.map(input, entity);
	}

	/**
	 * 更新之后
	 * 
	 * @param input
	 * @param entity
	 *            已保存的实体
	 * @param oldEntity
	 *            旧实体
	 * @param query
	 *            查询
	 * @return
	 */
	protected TOutput updateAfter(TUpdateInput input, TEntity entity, TEntity oldEntity, EntityQuery<TEntity> query) {
		if (this.getEntityClass().equals(this.getQueryEntityClass())) {
			return this.toOutputByEntity(entity);
		}
		return this.queryById(entity.getId());
	}

	/**
	 * 创建基于Id的实体查询
	 * 
	 * @param id
	 *            主键id值
	 * @return
	 */
	protected final EntityQuery<TEntity> createQueryByEntity(TPrimaryKey id) {
		EntityQuery<TEntity> query = new EntityQuery<>(this.getEntityClass());
		query.eq(IEntity.FILED_ID, id);
		this.systemByEntityCriteria(query);
		return query;
	}

	/**
	 * 创建基于Id的业务查询
	 * 
	 * @param id
	 *            主键id值
	 * @return
	 */
	protected final EntityQuery<TQueryEntity> createQuery(TPrimaryKey id) {
		EntityQuery<TQueryEntity> query = new EntityQuery<>(this.getQueryEntityClass());
		query.eq(IEntity.FILED_ID, id);
		this.systemByCriteria(query);
		return query;
	}

	/**
	 * 实体系统条件处理
	 * 
	 * @param query
	 *            查询
	 */
	protected void systemByEntityCriteria(EntityQuery<TEntity> query) {
		if (this.isSoftDeleteEntity()) {
			query.eq(ISoftDelete.FILED_IS_DELETE, false);
		}
	}

	/**
	 * 获取实体
	 * 
	 * @param id
	 * @return
	 */
	protected TEntity getEntity(TPrimaryKey id) {
		Query query = this.createQueryByEntity(id);
		return this.getRepository().selectFirst(query.builderSection());
	}

	/**
	 * 获取实体
	 * 
	 * @param id
	 * @param mode
	 *            锁定
	 * @return
	 */
	protected TEntity getEntity(TPrimaryKey id, LockModeEnum mode) {
		Query query = this.createQueryByEntity(id);
		if (mode != null) {
			query.lock(mode);
		} else {
			query.lock(LockModeEnum.NONE);
		}
		return this.getRepository().selectFirst(query.builderSection());
	}

	/**
	 * 获取更新锁实体
	 * 
	 * @param id
	 * @return
	 */
	protected final TEntity getEntityByUpdateLock(TPrimaryKey id) {
		return this.getEntity(id, LockModeEnum.UPDATE);
	}

	/**
	 * 获取查询实体
	 * 
	 * @param id
	 * @return
	 */
	protected TQueryEntity getQueryEntity(TPrimaryKey id) {
		Query query = this.createQuery(id);
		return this.getQueryRepository().selectFirst(query.builderSection());
	}

	/**
	 * 获取查询实体
	 * 
	 * @param id
	 * @param mode
	 *            锁定
	 * @return
	 */
	protected TQueryEntity getQueryEntity(TPrimaryKey id, LockModeEnum mode) {
		Query query = this.createQuery(id);
		if (mode != null) {
			query.lock(mode);
		} else {
			query.lock(LockModeEnum.NONE);
		}
		return this.getQueryRepository().selectFirst(query.builderSection());
	}

	/**
	 * 获取更新锁实体
	 * 
	 * @param id
	 * @return
	 */
	protected final TQueryEntity getQueryEntityByUpdateLock(TPrimaryKey id) {
		return this.getQueryEntity(id, LockModeEnum.UPDATE);
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public TOutput update(TUpdateInput input) {
		ExceptionUtils.checkNotNull(input, "input");
		ExceptionUtils.checkNotNull(input.getId(), "id");
		if (input instanceof DataValidation) {
			DataValidation validation = (DataValidation) input;
			validation.valid();
		}
		TEntity entity = this.getEntity(input.getId(), LockModeEnum.UPDATE);
		if (entity == null) {
			ExceptionUtils.throwValidationException("无法修改不存在的数据。");
		}
		TEntity oldEntity;
		try {
			oldEntity = this.getEntityClass().newInstance();
		} catch (InstantiationException | IllegalAccessException e) {
			this.getLogger().error(e.getMessage(), e);
			throw ExceptionUtils.throwValidationException("类型　" + this.getEntityClass().getName() + " 未提供默认构造函数。");
		}
		AutoMapUtils.map(entity, oldEntity);
		EntityQuery<TEntity> query = new EntityQuery<>(this.getEntityClass());
		updateBefore(input, entity, query);
		updateAuditing(entity);
		this.entityHandle(entity);
		this.getRepository().update(entity);
		query.reset();
		return this.updateAfter(input, entity, oldEntity, query);
	}

	/**
	 * 实体输出
	 * 
	 * @param entity
	 *            实体
	 * @return
	 */
	protected TOutput toOutputByEntity(TEntity entity) {
		if (entity == null) {
			return null;
		}
		return AutoMapUtils.map(entity, this.getQutputClass());
	}

	/**
	 * 查询输出
	 * 
	 * @param queryEntity
	 *            查询实体
	 * @return
	 */
	protected TOutput toOutputByQuery(TQueryEntity queryEntity) {
		if (queryEntity == null) {
			return null;
		}
		return AutoMapUtils.map(queryEntity, this.getQutputClass());
	}

	@Override
	public TOutput queryById(TPrimaryKey id) {
		ExceptionUtils.checkNotNull(id, "id");
		TQueryEntity entity = this.getQueryEntity(id);
		return this.toOutputByQuery(entity);
	}	

	/**
	 * 删除之前
	 * 
	 * @param id
	 *            主键
	 * @return 返回 true 则调用删除
	 */
	protected void deleteBefore(TPrimaryKey id) {

	}

	/**
	 * 删除之后
	 * 
	 * @param id
	 *            主键
	 * @param isSoftDelete
	 *            是否是软删除
	 */
	protected void deleteAfter(TPrimaryKey id, boolean isSoftDelete) {

	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public void deleteById(TPrimaryKey id) {
		ExceptionUtils.checkNotNull(id, "id");
		this.deleteBefore(id);
		if (this.isSoftDeleteEntity()) {
			this.deleteAuditing(id);
		} else {
			this.getRepository().deleteByPrimaryKey(id);
		}
		this.deleteAfter(id, this.isSoftDeleteEntity());
	}
}
